Skip to content

Add ARIA roles and sort attributes to ProcessTable#18

Open
jbingham17 wants to merge 1 commit intomainfrom
fix/process-table-accessibility
Open

Add ARIA roles and sort attributes to ProcessTable#18
jbingham17 wants to merge 1 commit intomainfrom
fix/process-table-accessibility

Conversation

@jbingham17
Copy link
Contributor

@jbingham17 jbingham17 commented Feb 22, 2026

Summary

  • Added role="table" and aria-label to the process table container
  • Added role="columnheader" with aria-sort to sortable column headers
  • Added role="row" and aria-selected to process rows

Test plan

  • Verify table sorting still works correctly
  • Confirm screen readers announce sort state and table structure
  • Check that row selection is announced via aria-selected

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved ProcessTable accessibility with enhanced screen reader support. The process list table now properly communicates its structure, sortable columns, and selected row state to assistive technologies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

The PR adds comprehensive ARIA accessibility attributes and semantic roles to the ProcessTable component, including role declarations for tables, rows, and cells, along with aria-sort and aria-selected attributes. Additionally, package.json is updated with changes to dependencies or configurations.

Changes

Cohort / File(s) Summary
Accessibility Enhancements
src/components/ProcessTable.tsx
Added ARIA attributes (role="table", role="row", role="columnheader") and semantic properties (aria-label, aria-sort, aria-selected) to improve screen reader compatibility. No functional logic changes.
Configuration Updates
package.json
Updated dependencies or version configurations; review specific package changes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A table now speaks in accessible ways,
With ARIA whispers through all of its days,
Screen readers rejoice, they know every row,
Which columns can sort and which states they show,
Selected or not—semantics take flight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Add ARIA roles and sort attributes to ProcessTable' clearly and accurately describes the primary change in the changeset, which is adding accessibility attributes to the ProcessTable component.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/process-table-accessibility

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/ProcessTable.tsx (1)

99-167: ⚠️ Potential issue | 🟠 Major

Add role="cell" to data cells for valid ARIA table structure.

role="row" must own cell-type elements (cell, columnheader, rowheader, or gridcell per WAI-ARIA 1.2). Data cells on lines 155+ are plain span elements without explicit roles, breaking table semantics for screen readers. Add role="cell" to each data cell span.

Proposed fix
             <span className="col-pid">{process.pid}</span>
-            <span className="col-user">{process.user.substring(0, 8)}</span>
-            <span className="col-cpu" style={{ color: getCpuColor(process.cpu) }}>
+            <span role="cell" className="col-user">{process.user.substring(0, 8)}</span>
+            <span role="cell" className="col-cpu" style={{ color: getCpuColor(process.cpu) }}>
               {process.cpu.toFixed(1).padStart(5, ' ')}
             </span>
-            <span className="col-mem" style={{ color: getMemColor(process.mem) }}>
+            <span role="cell" className="col-mem" style={{ color: getMemColor(process.mem) }}>
               {process.mem.toFixed(1).padStart(5, ' ')}
             </span>
-            <span className="col-virt">{process.vsz}</span>
-            <span className="col-res">{process.rss}</span>
-            <span className="col-state">{process.stat.charAt(0)}</span>
-            <span className="col-time">{process.time}</span>
-            <span className="col-command">{process.command}</span>
+            <span role="cell" className="col-virt">{process.vsz}</span>
+            <span role="cell" className="col-res">{process.rss}</span>
+            <span role="cell" className="col-state">{process.stat.charAt(0)}</span>
+            <span role="cell" className="col-time">{process.time}</span>
+            <span role="cell" className="col-command">{process.command}</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ProcessTable.tsx` around lines 99 - 167, The data cell spans
inside the rendered rows (the map over filteredAndSortedProcesses in
ProcessTable.tsx) lack ARIA cell roles; update each span that renders process
fields (the spans for pid, user, col-cpu, col-mem, col-virt, col-res, col-state,
col-time, col-command) to include role="cell" so the row (role="row") contains
proper cell elements for screen readers, preserving existing props like
className, style, onClick behavior and values produced by
getCpuColor/getMemColor, selectedPid, and process.field usage.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 422e523 and 9c52381.

📒 Files selected for processing (1)
  • src/components/ProcessTable.tsx
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ProcessTable.tsx`:
- Around line 101-142: The sortable column header spans (those with class
"sortable" for PID, USER, CPU%, MEM%, and COMMAND) are not keyboard-accessible;
update each span (e.g., the spans calling handleSort('pid'), handleSort('user'),
handleSort('cpu'), handleSort('mem'), handleSort('command')) to be focusable by
adding tabIndex={0} and add an onKeyDown handler that listens for Enter and
Space and invokes the same handleSort(...) call; ensure existing aria-sort logic
using sortField and sortDirection remains unchanged so screen readers still get
state.

---

Outside diff comments:
In `@src/components/ProcessTable.tsx`:
- Around line 99-167: The data cell spans inside the rendered rows (the map over
filteredAndSortedProcesses in ProcessTable.tsx) lack ARIA cell roles; update
each span that renders process fields (the spans for pid, user, col-cpu,
col-mem, col-virt, col-res, col-state, col-time, col-command) to include
role="cell" so the row (role="row") contains proper cell elements for screen
readers, preserving existing props like className, style, onClick behavior and
values produced by getCpuColor/getMemColor, selectedPid, and process.field
usage.

Comment on lines 101 to 142
<span
role="columnheader"
className="col-pid sortable"
onClick={() => handleSort('pid')}
aria-sort={sortField === 'pid' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
PID{getSortIndicator('pid')}
</span>
<span
role="columnheader"
className="col-user sortable"
onClick={() => handleSort('user')}
aria-sort={sortField === 'user' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
USER{getSortIndicator('user')}
</span>
<span
role="columnheader"
className="col-cpu sortable"
onClick={() => handleSort('cpu')}
aria-sort={sortField === 'cpu' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
CPU%{getSortIndicator('cpu')}
</span>
<span
role="columnheader"
className="col-mem sortable"
onClick={() => handleSort('mem')}
aria-sort={sortField === 'mem' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
MEM%{getSortIndicator('mem')}
</span>
<span className="col-virt">VIRT</span>
<span className="col-res">RES</span>
<span className="col-state">S</span>
<span className="col-time">TIME</span>
<span role="columnheader" className="col-virt">VIRT</span>
<span role="columnheader" className="col-res">RES</span>
<span role="columnheader" className="col-state">S</span>
<span role="columnheader" className="col-time">TIME</span>
<span
role="columnheader"
className="col-command sortable"
onClick={() => handleSort('command')}
aria-sort={sortField === 'command' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/ProcessTable.tsx | head -200 | tail -120

Repository: coderabbit-demo/btop

Length of output: 5370


🌐 Web query:

ARIA sortable column header keyboard accessibility tabindex enter space

💡 Result:

  • Put the sorting control in the header as a real <button> (e.g., wrap the header text in a button inside <th>). Then you get keyboard focus via Tab and activation with Enter/Space for free—no tabindex needed on the <th>. (w3.org)
  • Use aria-sort on the <th> of the currently sorted column only, toggling ascending/descending as sort changes, and move/remove aria-sort when a different column becomes sorted. (w3.org)
  • Don’t rely on role="columnheader" for keyboard behavior: columnheader has no special keyboard interaction; the button is what must be operable. (developer.mozilla.org)

Minimal pattern:

<th scope="col" aria-sort="ascending">
  <button type="button">Company</button>
</th>
<th scope="col">
  <button type="button">Last name</button>
</th>

If you can’t use a real <button>, make the control focusable and clickable:

  • tabindex="0", role="button", and handle Enter + Space (typically keydown for Space with preventDefault() so the page doesn’t scroll).

Citations:


Sortable headers lack keyboard accessibility.

The sortable column headers (pid, user, cpu, mem, command at lines 101–142) have onClick handlers but are not focusable and cannot be activated via keyboard. Add tabIndex={0} and onKeyDown handlers for Enter/Space to all five sortable headers, or replace with <button> elements inside the columnheader spans for built-in keyboard support.

Proposed fix (apply to all sortable headers)
         <span
           role="columnheader"
           className="col-pid sortable"
           onClick={() => handleSort('pid')}
+          tabIndex={0}
+          onKeyDown={(e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+              e.preventDefault();
+              handleSort('pid');
+            }
+          }}
           aria-sort={sortField === 'pid' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
         >
           PID{getSortIndicator('pid')}
         </span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span
role="columnheader"
className="col-pid sortable"
onClick={() => handleSort('pid')}
aria-sort={sortField === 'pid' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
PID{getSortIndicator('pid')}
</span>
<span
role="columnheader"
className="col-user sortable"
onClick={() => handleSort('user')}
aria-sort={sortField === 'user' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
USER{getSortIndicator('user')}
</span>
<span
role="columnheader"
className="col-cpu sortable"
onClick={() => handleSort('cpu')}
aria-sort={sortField === 'cpu' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
CPU%{getSortIndicator('cpu')}
</span>
<span
role="columnheader"
className="col-mem sortable"
onClick={() => handleSort('mem')}
aria-sort={sortField === 'mem' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
MEM%{getSortIndicator('mem')}
</span>
<span className="col-virt">VIRT</span>
<span className="col-res">RES</span>
<span className="col-state">S</span>
<span className="col-time">TIME</span>
<span role="columnheader" className="col-virt">VIRT</span>
<span role="columnheader" className="col-res">RES</span>
<span role="columnheader" className="col-state">S</span>
<span role="columnheader" className="col-time">TIME</span>
<span
role="columnheader"
className="col-command sortable"
onClick={() => handleSort('command')}
aria-sort={sortField === 'command' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
<span
role="columnheader"
className="col-pid sortable"
onClick={() => handleSort('pid')}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleSort('pid');
}
}}
aria-sort={sortField === 'pid' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
PID{getSortIndicator('pid')}
</span>
<span
role="columnheader"
className="col-user sortable"
onClick={() => handleSort('user')}
aria-sort={sortField === 'user' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
USER{getSortIndicator('user')}
</span>
<span
role="columnheader"
className="col-cpu sortable"
onClick={() => handleSort('cpu')}
aria-sort={sortField === 'cpu' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
CPU%{getSortIndicator('cpu')}
</span>
<span
role="columnheader"
className="col-mem sortable"
onClick={() => handleSort('mem')}
aria-sort={sortField === 'mem' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
MEM%{getSortIndicator('mem')}
</span>
<span role="columnheader" className="col-virt">VIRT</span>
<span role="columnheader" className="col-res">RES</span>
<span role="columnheader" className="col-state">S</span>
<span role="columnheader" className="col-time">TIME</span>
<span
role="columnheader"
className="col-command sortable"
onClick={() => handleSort('command')}
aria-sort={sortField === 'command' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ProcessTable.tsx` around lines 101 - 142, The sortable column
header spans (those with class "sortable" for PID, USER, CPU%, MEM%, and
COMMAND) are not keyboard-accessible; update each span (e.g., the spans calling
handleSort('pid'), handleSort('user'), handleSort('cpu'), handleSort('mem'),
handleSort('command')) to be focusable by adding tabIndex={0} and add an
onKeyDown handler that listens for Enter and Space and invokes the same
handleSort(...) call; ensure existing aria-sort logic using sortField and
sortDirection remains unchanged so screen readers still get state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant